﻿#region Copyright

// All Material Copyright © 2008-2010 Dynamic Concepts Development Corp., All Rights Reserved
// 
// For further information please contact SoftwareSupport@dynconcepts.com
// 
// Dynamic Concepts Development Corp.  311 W 57th St. Suite #422, New York, NY 10019

#endregion

#region Using References

using System;
using System.Collections.Generic;
using System.IO;

#endregion

namespace DynConDev.CorpLib.CommandLineParser
{
    public class Parser
    {
        private const string c_OptionDelimiter = "/";
        private const string c_OptionValueDelimiter = ":";
        private readonly OptionCollection r_ExecutableOptions = new OptionCollection();
        private readonly List<string> r_ValidCommands = new List<string>();
        private CommandLine m_CommandLine;
        private int m_CurrentIndex;
        private Parameter m_CurrentParameter;
        private ParseState m_CurrentState;
        private bool m_Done;
        private string[] m_RawArgs;

        private Parser(IEnumerable<string> validCommands)
        {
            foreach (string validCommand in validCommands)
            {
                r_ValidCommands.Add(validCommand.ToUpperInvariant());
            }
        }

        public static CommandLine Process(string[] args, IEnumerable<string> validCommands)
        {
            Parser instance = new Parser(validCommands);
            return instance.Parse(args);
        }

        private Option ParseOption(string currentArg)
        {
            if (!currentArg.StartsWith(c_OptionDelimiter))
                throw new CommandLineParserException();
            string name;
            string value;

            int delimIndex = currentArg.IndexOf(c_OptionValueDelimiter);
            if (delimIndex > 0)
            {
                name = currentArg.Substring(1, delimIndex - 1);
                value = currentArg.Substring(delimIndex + 1);
            }
            else
            {
                name = currentArg.Substring(1);
                value = String.Empty;
            }
            return new Option(name, value);
        }

        private void ProcessExecutableOption(string currentArg)
        {
            r_ExecutableOptions.Append(ParseOption(currentArg));
            ++m_CurrentIndex;
        }

        private void ProcessCommandOption(string currentArg)
        {
            m_CommandLine.Command.CommandOptions.Append(ParseOption(currentArg));
            ++m_CurrentIndex;
        }

        private void ProcessParameterOption(string currentArg)
        {
            m_CurrentParameter.ParameterOptions.Append(ParseOption(currentArg));
            ++m_CurrentIndex;
        }

        private string[] ProcessParametersFile(string filename)
        {
            List<string> args = new List<string>();
            using (StreamReader sr = new StreamReader(filename))
            {
                while (!sr.EndOfStream)
                {
                    string arg = sr.ReadLine().Trim();
                    args.Add(arg);
                }
            }

            return args.ToArray();
        }

        private CommandLine Parse(string[] args)
        {
            if ((args.Length == 1) && args[0].StartsWith("@"))
                args = ProcessParametersFile(args[0].Substring(1));

            m_RawArgs = args;
            m_CurrentState = ParseState.ExecutableOptions;
            m_CurrentIndex = 0;
            m_Done = false;
            while (!m_Done)
            {
                string currentArg = m_RawArgs[m_CurrentIndex];
                switch (m_CurrentState)
                {
                    case ParseState.ExecutableOptions:
                        if (currentArg.StartsWith(c_OptionDelimiter))
                            ProcessExecutableOption(currentArg);

                        else
                        {
                            m_CurrentState = ParseState.CommandName;
                        }
                        break;
                    case ParseState.CommandName:
                        if (currentArg.StartsWith(c_OptionDelimiter))
                            throw new CommandLineParserException();
                        CreateCommandLine(currentArg);
                        m_CurrentState = ParseState.CommandOption;
                        break;
                    case ParseState.CommandOption:
                        if (currentArg.StartsWith(c_OptionDelimiter))
                        {
                            ProcessCommandOption(currentArg);
                        }
                        else
                        {
                            m_CurrentState = ParseState.ParameterName;
                        }
                        break;
                    case ParseState.ParameterName:
                        if (currentArg.StartsWith(c_OptionDelimiter))
                            throw new CommandLineParserException();
                        m_CurrentParameter = new Parameter(currentArg);
                        m_CommandLine.Parameters.Append(m_CurrentParameter);
                        ++m_CurrentIndex;
                        m_CurrentState = ParseState.ParameterOption;
                        break;
                    case ParseState.ParameterOption:
                        if (currentArg.StartsWith(c_OptionDelimiter))
                        {
                            ProcessParameterOption(currentArg);
                        }
                        else
                        {
                            m_CurrentState = ParseState.ParameterName;
                        }
                        break;
                }
                if (m_CurrentIndex == m_RawArgs.Length)
                {
                    m_Done = true;
                }
            }
            return m_CommandLine;
        }

        private void CreateCommandLine(string commandName)
        {
            if (!r_ValidCommands.Contains(commandName.ToUpperInvariant()))
            {
                throw new CommandLineParserException("Illegal Command Name");
            }
            m_CommandLine = new CommandLine(commandName, r_ExecutableOptions);
            ++m_CurrentIndex;
        }

        #region Nested type: ParseState

        private enum ParseState
        {
            ExecutableOptions,
            CommandName,
            CommandOption,
            ParameterName,
            ParameterOption
        }

        #endregion
    }
}